<?php

/**
 * +------ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER * ---------+
 * +----------------------------------------------------------------------------+
 * | PHPKiisf - Keep It in Simple Framework										|
 * | Version 1.0								|
 * | Copyright (C) <2013>  Mario Cezzare <mcezzare@gmail.com>			|
 * | Blog: http://mcezzare.blogspot.com.br					|
 * |										|
 * | This program is part of PHPKiisf, and is free software: you can		|
 * | redistribute it and/or modify it under the terms of the GNU 		|
 * | General Public License as published by					|
 * | the Free Software Foundation, either version 3 of the License, or		|
 * | (at your option) any later version.					|
 * |										|
 * | This program is distributed in the hope that it will be useful,		|
 * | but WITHOUT ANY WARRANTY; without even the implied warranty of		|
 * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the		|
 * | GNU General Public License for more details.				|
 * | You should have received a copy of the GNU General Public License		|
 * | along with this program.  If not, see <http://www.gnu.org/licenses/>.	|
 * +----------------------------------------------------------------------------+
 * | Author : <Mario Cezzare Angelicola Chiodi> <mcezzare@gmail.com>		|
 * +----------------------------------------------------------------------------+
 * | Contributors								|
 * |										|
 * +----------------------------------------------------------------------------+
 *
 */
/**
 * @package	Lib
 * @access	public
 * @since	2013-7-01
 * @author	Mario Cezzare <mcezzare@gmail.com>
 */

/**
 * Class to generate Uml Diagrams from classes
 *
 * @since	2013-7-01
 * @author	Mario Cezzare <mcezzare@gmail.com>
 */
class phpuml {

	private $version = 2.0;

	/* Base folder , will look recursive  */
	public $start_path;

	/**
	 * [__construct description]
	 *
	 * @method __construct
	 *
	 * @param  [type]      $path [description]
	 *
	 * @since	2013-7-01
	 * @author	Mario Cezzare <mcezzare@gmail.com>
	 */
	public function __construct($path = null) {
		if ($path == null) {
			if (php_sapi_name() == 'cli') {
				$this->start_path = __DIR__;
				// $this->get_files( $this->start_path , true );
			}
		}
	}

	/**
	 * Simple caller
	 *
	 * @method get_list_files
	 * @param  boolean        $print_steps Like a debug, print steps for each item
	 * @return [type]                      [description]
	 *
	 * @since	2013-7-02
	 * @author	Mario Cezzare <mcezzare@gmail.com>
	 */
	public function get_list_files($print_steps = true) {
		return $this->get_files($this->start_path, $print_steps);
	}

	/**
	 *
	 * @var Array Store the arrays of the recursive function get_files
	 */
	public $list = array();

	/**
	 *
	 * @var Array Store the classes organized in properties
	 */
	public $list_classes = array();

	/**
	 * To store base/parent classes
	 *
	 * @var array
	 */
	public $parent_classes = array();

	/**
	 * To store only the files to be analyzed in one section
	 *
	 * @var array
	 */
	public $classes_to_parse = array();

	/**
	 * Get list of files, recursive and store an array of folders with files in the public atribute $list
	 *
	 *
	 * @method get_files
	 *
	 * @param  String    $topdir      				Folder to examine
	 * @param  boolean   $print_steps 				Like a debug, print steps for each item
	 * @return Array     on each recursive step   	array("folder" = array( 0 => "filename") )
	 *
	 * @since	2013-7-02
	 * @author	Mario Cezzare <mcezzare@gmail.com>
	 */
	public function get_files($topdir, $print_steps = false, $filter = true) {
		/* List of folders to exclude - Complete name */
		$exclude_pattern = "/^(__resources|public_files|phpdocs|phpkiisf-docs|ftpsync.settings)/";
		/* List of files to exclude - start with dot . */
		$hidden_files = '/^[\.|\..]/';

		/* Initialize array */
		$folder_collection = array();

		if (is_dir($topdir)) {
			/* save parent folder */
			$folder_collection[] = $topdir;

			/* auto indexed numeric keys
			  array_push( $this->list , $topdir );
			 */
			//named keys - folder ok - will contaim an array of files
			$this->list{$topdir} = array();
			$index = 0;

			$files_collection[] = null;
			foreach (scandir($topdir) as $item) {
				/* exclude hidden files .svn/ , .git/ .DS_Store .htaccess , etc and a list defined on $exclude_pattern */

				if (!preg_match($hidden_files, $item) && !preg_match($exclude_pattern, $item)) {
					$folder = "$topdir/$item";
					if (is_dir($folder)) {
						echo $print_steps === true ? "[$item]\n" : null;
						$this->get_files($folder, $print_steps, $filter);
					} else {
						echo $print_steps === true ? "-File: $item\n" : null;
						/* Only files in files list, exclude folders */
						array_push($this->list{$topdir}, $item);
					}

					/* If want to include folders in list of files too
					  array_push( $this->list{$topdir} , $item );
					 */
				}

				$index++;
			}
		} else {
			echo "Not a directory: $topdir";
		}

		/**
		 * REMEMBER , this is recursive  and will return only the topdir
		 */
		return $this->get_list();
	}

	/**
	 * Parse Lines from php code file loking for the following regexp
	 *
	 * @method detect_class
	 * @param  [type]       $filename [description]
	 * @return [type]                 [description]
	 *
	 * @since	2013-7-04
	 * @author	Mario Cezzare <mcezzare@gmail.com>
	 */
	public function detect_class($filename) {
		$tmp_class = null;

		if (file_exists($filename)) {
			$handle = fopen($filename, "r");

			while ($line = fgets($handle)) {
				$line = trim($line); //."\n" ;
				// achei classe
				// search class words {\n
				if (preg_match("/^(class)\s*\s.+(\{)/", $line) || preg_match("/^(interface|abstract|final)\s*\s.+(\{)/", $line)) {

					// echo "$line" ;
					$tmp_class = $this->array_to_class(explode(" ", $line));
					// var_dump( $tmp_class );
					$this->list_classes[] = $this->organize_array_to_class($tmp_class);

					// to diags on console mode
					return $filename;
				}
			}
		}
	}

	/**
	 * Cleanup an array and return a array of properties of a class
	 * Remove keys with values () , {
	 * @method array_to_class
	 *
	 * @param  [type]         $array [description]
	 * @return [type]                [description]
	 *
	 *
	 * @since	2013-7-03
	 * @author	Mario Cezzare <mcezzare@gmail.com>
	 */
	public function array_to_class($array) {
		$tmp_array = null;
		if (is_array($array)) {
			for ($i = 0; $i < count($array); $i++) {
				if ($array[$i] != "{") {
					$tmp_array[$i] = str_replace("()", "", $array[$i]);
				}
			}
		}
		return $tmp_array;
	}

	/**
	 * Organize the array to scan the classes
	 *
	 * @method organize_array_to_class
	 *
	 * @param  [type]                  $array [description]
	 * @return [type]                         [description]
	 *
	 * @since	2013-7-04
	 * @author	Mario Cezzare <mcezzare@gmail.com>
	 */
	public function organize_array_to_class($array) {
		$tmp_array = array(
		    "name" => null, // name class
		    "modifier" => null, // final , abstract
		    "type" => null, //  class , interface
		    "extends" => false, // if is extended class
		    "parent" => null,
		    "implements" => false,
		);

		/**
		 * check if have these values
		 */
		$tmp_array{"extends"} = array_search('extends', $array) ? true : false;
		$tmp_array{"implements"} = array_search('implements', $array) ? true : false;


		if (is_array($array)) {
			switch (count($array)) {
				/**
				 * Ex:
				 * 0 => string 'class'
				 * 1 => string 'utils'
				 * 			OR
				 * 0 => string 'interface'
				 * 1 => string 'test_interface'
				 */
				case 2 :
					if ($array[0] == "class" || $array[0] == "interface") {
						$tmp_array{"type"} = $array[0];
						$tmp_array{"name"} = $array[1];
					} else {
						$tmp_array = $array;
					}

					break;

				/**
				 * Ex:
				 * 0 => string 'final'
				 * 1 => string 'class'
				 * 2 => string 'config'
				 * 		OR
				 * 0 => string 'abstract'
				 * 1 => string 'class'
				 * 2 => string 'abs_test'
				 */
				case 3 :
					if ($array[0] == "final" || $array[0] == "abstract") {
						$tmp_array{"modifier"} = $array[0];
						$tmp_array{"type"} = $array[1];
						$tmp_array{"name"} = $array[2];
					} else {
						$tmp_array = $array;
					}
					break;

				/**
				 * Ex:
				 * 0 => string 'class' ,
				 * 1 => string 'dispatcher' ,
				 * 2 => string 'extends' ,
				 * 3 => string 'default_controller'
				 * 			OR
				 * 0 => string 'interface'
				 * 1 => string 'test_interface'
				 * 2 => string 'implements'
				 * 3 => string 'mysql_driver'
				 */
				case 4 :
					if ($array[0] == "class" && $array[2] == "extends") {
						$tmp_array{"type"} = $array[0];
						$tmp_array{"name"} = $array[1];
						// $tmp_array{"extends"} 	= true ;
						$tmp_array{"parent"} = $array[3];
					} elseif ($array[0] == "interface") {
						$tmp_array{"type"} = $array[0];
						$tmp_array{"name"} = $array[1];
						$tmp_array{"parent"} = $array[3];
					} else {
						$tmp_array = $array;
					}
					break;

				/**
				 * Ex:
				 * 0 => string 'final' ,
				 * 1 => string 'class' ,
				 * 2 => string 'not_found_exception' ,
				 * 3 => string 'extends' ,
				 * 4 => string 'Exception'
				 */
				case 5 :
					if (preg_match("/(final|abstract)/", $array[0]) && preg_match("/(extends|implements)/", $array[3])) {
						$tmp_array{"modifier"} = $array[0];
						$tmp_array{"type"} = $array[1];
						$tmp_array{"name"} = $array[2];
						$tmp_array{"parent"} = $array[4];
					} else {
						$tmp_array = $array;
					}

					break;

				/**
				 * If I forget a possible case, please add it on cases defining by reserved keyword position
				 * acording to the returned array
				 */
				default :
					$tmp_array = $array;

					break;
			}
		}
		//Store the parent classes that can be instantiated
		if ($tmp_array{"parent"} == null && $tmp_array{"modifier"} != "abstract") {
			$this->parent_classes[] = $tmp_array;
		}

		return $tmp_array;
	}

	/**
	 * used by autoloader
	 *
	 * @method init
	 * @return [type] [description]
	 */
	public static function init() {
		self::__construct();
	}

	/**
	 * Manual Geters and Setters
	 */
	public function get_start_path() {
		return $this->start_path;
	}

	public function set_start_path($start_path) {
		$this->start_path = $start_path;
		return $this;
	}

	public function get_list() {
		return $this->list;
	}

	public function set_list(Array $list) {
		$this->list = $list;
		return $this;
	}

	/**
	 * Drawing Section
	 */
	public function create_box($array) {
		$content = null;
		$content_text = null;
		$modifier_description = null;
		$box =
			"<div style=\"border:solid 1px #EA9C5C; width: 300px; padding:3px;\">
			<u>%s</u>
			<br>
			%s <br>
			</div> <hr>";
		if (is_array($array)) {
			$class = new $array{"name"}();
			$properties = $this->get_reflection($class);
			$i = 1;

			foreach ($properties as $property) {
				$modifier_description = $this->get_bitmask_description($property->getModifiers());

				$content_text .=sprintf(" %s %s  <br>", $modifier_description, $property->getName());
				$i++;
			}
			//@todo colocar o array de metodos dentro do box
			// utils::show_object(get_class_methods($class)
			printf($box, $array{"type"} . " <b>" . $array{"name"} . "</b>", $content_text);
		}
	}

	/**
	 * [get_atributes description]
	 *
	 * @method get_atributes
	 *
	 * @param  [type]        $class [description]
	 * @return [type]               [description]
	 */
	public function get_atributes($class) {
		$content = null;

		if (is_object($class)) {
			$object = new $class();
			$content = get_object_vars($class);
		}

		return $content;
	}

	/**
	 * Use Reflection to get properties of class
	 *
	 * @param type $class
	 * @return type
	 */
	public function get_reflection($class) {
		$content = null;

		if (is_object($class)) {
			$object = new $class();
			$ref = new ReflectionClass($object);
			$content = $ref->getProperties();
		}

		return $content;
	}

	/**
	 *
	 * @param type $code
	 * @return string
	 */
	public function get_bitmask_description($code) {
		$type = "undefined";
		if (is_numeric($code)) {
			switch ($code) {
				case 256: //public
					$type = "public ";
					break;
				case 257: //public static
					$type = "public static ";
					break;
				case 512: //protected
					$type = "protected ";
					break;
				case 513: //protected
					$type = "protected static ";
					break;
				case 1024: //private
					$type = "private ";
					break;
				case 1025: //private static
					$type = "private static ";
					break;


				default:
					$type = $code;
					break;
			}
		}
		return $type;
	}

	//here are some functions to run in console mode
	public function get_list_files_to_parse($print = false) {
		$clean_list = array();
		$list_files = $this->get_list_files(false);
		foreach ($list_files as $key => $value) {
			$have_files = is_array($value);
			$print == true ? print "[$key]:\n"  : null;
			foreach ($value as $folder => $filename) {

				if (substr($filename, -3) == "php" && $filename != "empty.php") {
					$print == true ? print "-$filename\n"  : null;
					array_push($clean_list, "$key/$filename");
				}
			}
		}
		return $clean_list;
	}

}

//end class

/**
 * Console mode
 */
if (php_sapi_name() == 'cli' || PHP_SAPI == 'cli') {

	$uml = new phpuml();
	$uml->start_path = "/Users/mcezzare/Sites/_DEVELOP/phpkiisf";
	$include_files = array();

	// ini_set( 'include_path' , get_include_path() . ":" . $uml->start_path );
	require_once $uml->start_path . "/includes/functions.php";
	require_once $uml->start_path . "/lib/utils.class.php";
	require_once $uml->start_path . "/lib/config.class.php";
	require_once $uml->start_path . "/lib/message.class.php";
	$config = new config();


	print "Running in command line.\nReading: " . $uml->start_path . " \n";
	set_include_path(sprintf(" %s%s%s/", get_include_path(), PATH_SEPARATOR, $uml->start_path));
	print "Libs Path:" . get_include_path() . "\n";

	$list_files = $uml->get_list_files(false);

	$clean_list = $uml->get_list_files_to_parse(false);
	for ($f = 0; $f < count($clean_list); $f++) {
		$filename_full = $clean_list[$f];
		// echo $filename_full . "\n";
		if ($uml->detect_class($filename_full)) {
			// include_once $filename_full;
			$include_files[] = $filename_full;
		}
	}

	printf("Total classes: %d \n", count($uml->list_classes));
	printf("Parent classes:  %d  \n", count($uml->parent_classes));
	foreach ($include_files as $key => $file) {
		include_once $file;
	}

	foreach ($uml->parent_classes as $class => $item) {

		$uml->create_box($item);
	}
}
?>
